package com.atdel.demo26;

import com.atdel.demo23.Student;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;



/**
 * @BelongsProject: java_study
 * @BelongsPackage: com.atdel.demo26
 * @Author: Del
 * @CreateTime: 2025-03-25  09:38
 * @Description: 使用反射内省 实现
 * @Version: 1.0
 */
public class BeanUtils {
    // 缓存 BeanInfo，避免重复调用 Introspector.getBeanInfo
    private static final Map<Class<?>, BeanInfo> BEAN_INFO_CACHE = new ConcurrentHashMap<>();

    /**
     * @author: Del
     * @date: 2025/3/25 9:45
     * @param bean 目标对象
     * @param propertyName 属性名
     * @param value 属性值
     * @description:  设置 JavaBean 的属性值
     */
    public static void setProperty(Object bean, String propertyName, Object value) {
        if (bean == null || propertyName == null || propertyName.isEmpty()) {
            throw new IllegalArgumentException("参数不能为空");
        }

        try {
            // 获取 BeanInfo 并缓存
            BeanInfo beanInfo = getBeanInfoFromCache(bean.getClass());

            // 查找指定属性的 PropertyDescriptor
            PropertyDescriptor propertyDescriptor = findPropertyDescriptor(beanInfo, propertyName);

            // 调用 setter 方法设置值
            invokeSetter(bean, propertyDescriptor, value);
        } catch (IllegalArgumentException e) {
            throw e; // 直接抛出非法参数异常
        } catch (Exception e) {
            throw new RuntimeException("设置属性失败: " + propertyName, e);
        }
    }

    /**
     * @author: Del
     * @date: 2025/3/25 9:45
     * @param bean 目标对象
     * @param propertyName 属性名
     * @return Object 属性值
     * @description: 获取 JavaBean 的属性值
     */
    public static Object getProperty(Object bean, String propertyName) {
        if (bean == null || propertyName == null || propertyName.isEmpty()) {
            throw new IllegalArgumentException("参数不能为空");
        }

        try {
            // 获取 BeanInfo 并缓存
            BeanInfo beanInfo = getBeanInfoFromCache(bean.getClass());

            // 查找指定属性的 PropertyDescriptor
            PropertyDescriptor propertyDescriptor = findPropertyDescriptor(beanInfo, propertyName);

            // 调用 getter 方法获取值
            return invokeGetter(bean, propertyDescriptor);
        } catch (IllegalArgumentException e) {
            throw e; // 直接抛出非法参数异常
        } catch (Exception e) {
            throw new RuntimeException("获取属性失败: " + propertyName, e);
        }
    }

    /**
     * @author: Del
     * @date: 2025/3/25 9:46
     * @param source 源对象
     * @param target 目标对象
     * @description: 复制某个对象的属性到另一个对象
     */
    public static void copyProperties(Object source, Object target) {
        // 检查源对象和目标对象是否为空，如果任一为空，则抛出非法参数异常
        if (source == null || target == null) {
            throw new IllegalArgumentException("参数不能为空");
        }

        try {
            // 从缓存中获取源对象的Bean信息
            BeanInfo sourceBeanInfo = getBeanInfoFromCache(source.getClass());
            // 从缓存中获取目标对象的Bean信息
            BeanInfo targetBeanInfo = getBeanInfoFromCache(target.getClass());

            // 获取源对象的所有属性描述符
            PropertyDescriptor[] sourcePds = sourceBeanInfo.getPropertyDescriptors();
            // 获取目标对象的所有属性描述符
            PropertyDescriptor[] targetPds = targetBeanInfo.getPropertyDescriptors();

            // 创建一个线程安全的映射，用于存储目标对象的属性名称和其对应的属性描述符
            Map<String, PropertyDescriptor> targetPdMap = new ConcurrentHashMap<>();
            for (PropertyDescriptor pd : targetPds) {
                targetPdMap.put(pd.getName(), pd);
            }

            // 遍历源对象的属性描述符，将属性值从源对象复制到目标对象
            for (PropertyDescriptor sourcePd : sourcePds) {
                String propertyName = sourcePd.getName();
                Method readMethod = sourcePd.getReadMethod();
                Method writeMethod = targetPdMap.get(propertyName).getWriteMethod();

                // 如果源对象的属性有读取方法且目标对象的属性有写入方法，则执行属性值复制
                if (readMethod != null && writeMethod != null) {
                    Object value = readMethod.invoke(source);
                    writeMethod.invoke(target, value);
                }
            }
        } catch (IllegalArgumentException e) {
            // 直接抛出非法参数异常
            throw e;
        } catch (Exception e) {
            throw new RuntimeException("复制属性失败", e);
        }
    }


    /**
     * @author: Del
     * @date: 2025/3/25 9:46
     * @param clazz 类对象
     * @return BeanInfo 对象
     * @throws IntrospectionException 如果无法获取 BeanInfo
     * @description: 从缓存中获取 BeanInfo，如果不存在则生成并缓存
     */
    private static BeanInfo getBeanInfoFromCache(Class<?> clazz) throws IntrospectionException {
        // 使用computeIfAbsent方法来避免重复的bean信息计算
        // 这里使用了lambda表达式作为computeIfAbsent的第二个参数
        // lambda表达式的参数c代表BEAN_INFO_CACHE尝试获取bean信息的类
        return BEAN_INFO_CACHE.computeIfAbsent(clazz, c -> {
            try {
                // 尝试获取指定类的Bean信息
                // Introspector.getBeanInfo方法可能会抛出IntrospectionException
                return Introspector.getBeanInfo(c);
            } catch (IntrospectionException e) {
                // 捕获IntrospectionException并将其包装为RuntimeException
                // 这样做是为了简化错误处理，因为RuntimeException不需要被强制捕获
                throw new RuntimeException(e);
            }
        });
    }

    /**
     * @author: Del
     * @date: 2025/3/25 9:47
     * @param beanInfo 对象
     * @param propertyName 属性名
     * @return PropertyDescriptor 对象
     * @throws IllegalArgumentException 如果找不到指定属性
     * @description: 查找指定属性的 PropertyDescriptor
     */
    private static PropertyDescriptor findPropertyDescriptor(BeanInfo beanInfo, String propertyName) {
        // 遍历属性描述符数组
        for (PropertyDescriptor pd : beanInfo.getPropertyDescriptors()) {
            // 检查当前属性描述符的名称是否与指定的属性名称匹配
            if (pd.getName().equals(propertyName)) {
                // 如果匹配，则返回该属性描述符
                return pd;
            }
        }
        // 如果没有找到匹配的属性描述符，则抛出异常
        throw new IllegalArgumentException("属性不存在: " + propertyName);
    }

    /**
     * @author: Del
     * @date: 2025/3/25 9:48
     * @param bean 目标对象
     * @param propertyDescriptor 属性描述符
     * @param value 属性值
     * @throws IllegalAccessException 如果无法访问 setter 方法
     * @throws InvocationTargetException 如果 setter 方法抛出异常
     * @description: 调用 setter 方法设置值
     */
    private static void invokeSetter(Object bean, PropertyDescriptor propertyDescriptor, Object value)
            throws IllegalAccessException, InvocationTargetException {
        // 获取属性的 setter 方法
        Method writeMethod = propertyDescriptor.getWriteMethod();
        // 如果 setter 方法不存在，抛出异常
        if (writeMethod == null) {
            throw new IllegalArgumentException("属性没有 setter 方法: " + propertyDescriptor.getName());
        }
        // 调用 setter 方法设置属性值
        writeMethod.invoke(bean, value);
    }

    /**
     * @author: Del
     * @date: 2025/3/25 9:48
     * @param bean 目标对象
     * @param propertyDescriptor 属性描述符
     * @return Object 属性值
     * @throws IllegalAccessException 如果无法访问 getter 方法
     * @throws InvocationTargetException 如果 getter 方法抛出异常
     * @description: 调用 getter 方法获取值
     */
    private static Object invokeGetter(Object bean, PropertyDescriptor propertyDescriptor)
            throws IllegalAccessException, InvocationTargetException {
        // 获取属性的 getter 方法
        Method readMethod = propertyDescriptor.getReadMethod();
        // 如果属性没有 getter 方法，则抛出异常
        if (readMethod == null) {
            throw new IllegalArgumentException("属性没有 getter 方法: " + propertyDescriptor.getName());
        }
        // 使用反射调用 getter 方法获取属性值并返回
        return readMethod.invoke(bean);
    }

    public static void main(String[] args) {
        Student student01 = new Student(1002,"李四", 36, 99.0);
        Student student02 = new Student(1002,"李四", 89, 98.0);


        // 复制属性
        copyProperties(student01, student02);
        System.out.println(student01);
        System.out.println(student02);


        // 设置属性
        setProperty(student02, "name", "王五");

        System.out.println(student02);

        // 获取属性
        System.out.println(getProperty(student02, "name"));
        System.out.println(getProperty(student02, "age"));
    }
}
